﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Diagnostics;
using System.IO;

namespace SignToolUI.UI
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private string _timestampUrl = String.Empty;
        private string _friendlyName = String.Empty;
        private string _signtoolPath = String.Empty;
        private bool _isRfc3161;


        #region UI


        private void btnSelectCertificate_Click(object sender, EventArgs e)
        {
            ChooseCertificate();
        }

        private void cbTimestampProvider_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Select a specific Timestamp Provider
            switch (cbTimestampProvider.Text)
            {
                case "VeriSign / Symantec":
                    _timestampUrl = "http://timestamp.verisign.com/scripts/timstamp.dll";
                    _isRfc3161 = false;
                    break;
                case "Comodo":
                    _timestampUrl = "http://timestamp.comodca.com/authenticode";
                    _isRfc3161 = true;
                    break;
                case "StartCom":
                    _timestampUrl = "http://www.startssl.com/timestamp";
                    _isRfc3161 = true;
                    break;
                case "GlobalSign":
                    _timestampUrl = "http://timestamp.globalsign.com/scripts/timestamp.dll";
                    _isRfc3161 = true;
                    break;
                case "Starfield Tech":
                    _timestampUrl = "http://tsa.starfieldtech.com";
                    _isRfc3161 = true;
                    break;
                default:
                    _timestampUrl = "";
                    break;
            }
        }

        private void btnSign_Click(object sender, EventArgs e)
        {
            // Check depending instance variables
            if (_friendlyName == String.Empty)
            {
                return;
            }

            SignAssembly(chooseAssembly.FileName, _friendlyName);
            lblStatus.Text = "Done";
        }

        private void button1_Click(object sender, EventArgs e)
        {
            chooseAssembly.ShowDialog();

            if (chooseAssembly.FileName == String.Empty) return;

            lblFileName.Text = Path.GetFileName(chooseAssembly.FileName);
            var fi = new FileInfo(chooseAssembly.FileName);
            lblFileSize.Text = string.Format("{0} Bytes", fi.Length);
            btnSign.Enabled = true;
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            // Check the paths
            btnSign.Enabled = false;
            SearchForSignTool();
        }


        #endregion

        #region Signing

        /// <summary>
        /// Select Certificate to sign
        /// </summary>
        private void ChooseCertificate()
        {
            // Get Certificates from Windows Certification Store

            // Open Store readonly and get a collection of current valid X509 Certificates
            var store = new X509Store("MY", StoreLocation.CurrentUser);
            store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
            var certificates = store.Certificates;

            // Get valid Code-Signing Certificates only
            var validCertificates = certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, true);
            var validSigningCertificates = validCertificates.Find(X509FindType.FindByApplicationPolicy,
                                                                  "1.3.6.1.5.5.7.3.3",
                                                                  false);

            try
            {
                // Display selection Form and get result
                var selectedCertificate =
                    X509Certificate2UI.SelectFromCollection(validSigningCertificates, "Select Code-Signing-Certificate",
                                                            "Select a certificate to sign an assembly.",
                                                            X509SelectionFlag.SingleSelection, Handle)[0];

                if (selectedCertificate == null) return;

                // A certificate has been selected, display some information
                var cnPart = selectedCertificate.Subject.Split(',')[1];
                _friendlyName = cnPart.Substring(4, cnPart.Length - 4);

                lblIssuedFor.Text = _friendlyName;
                lblThumbprint.Text = selectedCertificate.Thumbprint;
                btnAssembly.Enabled = true;
            }
            catch (CryptographicException)
            {
                Console.WriteLine("Information could about the certificate could not be retrieved");
            }
            catch (ArgumentOutOfRangeException)
            {
                // There was no Certificate selected
            }
            finally
            {
                // Close Selection Store
                store.Close();
            }
        }

        /// <summary>
        /// Open signtool with specific parameters
        /// </summary>
        /// <param name="path">Path to the file</param>
        /// <param name="certificateName">certification name used to sign</param>
        private void SignAssembly(string path, string certificateName)
        {
            var p = new Process();
            var psi = new ProcessStartInfo();

            // Arguments
            if (_timestampUrl == String.Empty)
            {
                psi.Arguments = string.Format("sign /n \"{0}\" \"{1}\"", certificateName, path);
            }
            else
            {
                psi.Arguments =
                    string.Format(
                        _isRfc3161 ? "sign /n \"{0}\" /tr \"{1}\" \"{2}\"" : "sign /n \"{0}\" /t \"{1}\" \"{2}\"",
                        certificateName, _timestampUrl, path);
            }

            psi.CreateNoWindow = true;
            psi.WindowStyle = ProcessWindowStyle.Hidden;
            psi.FileName = _signtoolPath;


            p.StartInfo = psi;
            p.Start();
            p.WaitForExit();
            p.Close();
        }

        #endregion

        #region Searching for SignTool

        /// <summary>
        /// Search all common paths for signtool
        /// </summary>
        private void SearchForSignTool()
        {
            var sdkPath = GetSdkPath();

            if (sdkPath == null)
            {
                MessageBox.Show("You will need the signtool.exe from the Windows SDK to use this application.",
                                "There was no Windows-SDK found.",
                                MessageBoxButtons.OK,
                                MessageBoxIcon.Error);
            }
            else
            {
                // Check the paths for signtool.exe
                foreach (var path in sdkPath)
                {
                    if (!File.Exists(path + @"\Bin\signtool.exe")) continue;
                    // Found
                    _signtoolPath = path + @"\Bin\signtool.exe";
                    break;
                }

                // All folders checked, found?
                if (_signtoolPath == String.Empty)
                {
                    MessageBox.Show(
                        "There are one ore more SDK Folders but it seems none of them contains signtool.exe. Please update the features on your SDK installation.",
                        "signtool.exe not found",
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error);
                }
            }
        }


        /// <summary>
        /// Get the location of SDK-Path folder
        /// </summary>
        /// <returns></returns>
        private static IEnumerable<string> GetSdkPath()
        {
            // Root paths
            var path32 = string.Format("{0}\\Microsoft SDKs\\Windows\\",
                                       Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86));

            var path64 = string.Format("{0}\\Microsoft SDKs\\Windows\\",
                                       Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles));

            return Directory.Exists(path64)
                       ? Directory.GetDirectories(path64).ToList()
                       : (Directory.Exists(path32) ? Directory.GetDirectories(path32).ToList() : null);
        }


        #endregion

    }
}
